package songZhao.MinaDemo.Example;

import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

public class ExampleProtocolDecoder implements ProtocolDecoder{
	
	/**
	 * session 是可以设置属性Attribute的 (key,value),下边
	 * 这行代码,就是根据本类的名字和第二个参数的字符串,来生成一个属性key.
	 * 下边,用这个key来存储上下文的value,即之前的报文,用来防止半包的情况.
	 */
	private final AttributeKey CONTEXT = new AttributeKey(this.getClass(), "context");
	private final Charset charset;
	private int maxPackLength = 100;
	
	public ExampleProtocolDecoder(Charset charset) {
		this.charset = charset;
	}
	
	

	@Override
	public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
		Context context = this.getContext(session);
		//增加上context中的剩余数据
		context.append(in);
		//获取完整上下文中的buffer
		IoBuffer buffer = context.getBuffer();
		//准备从头开始读取
		buffer.flip();
		
		//声明操作变量
		int packLength ;
		int contentLength;
		byte flag;
		String content ;
		
		//上下文长度还不到一个包头,直接返回,等待下次追加候的上下文
		if (buffer.remaining() <5) {
			return;
		}else {
			/**
			 * 从这个位置开始的代码之后的读取操作,凡是没有正确完整的去除一个pack的情况.
			 * 均进行一次context的上下文清理.并且retrun.
			 * 正确读取则不用.
			 */
			while ((buffer.remaining()>=5)) {
				//读之前标记,并取出包头
				buffer.mark();
				packLength = buffer.getInt();
				flag = buffer.get();
				contentLength = packLength -5;
				//判断包是否合法.
				if (packLength > maxPackLength || contentLength <0 ||packLength <0) {
					//不合法,丢弃,重置上下文buffer
					buffer.free();
					context.setBuffer(IoBuffer.allocate(80,false).setAutoExpand(true));
					return;
				}
				if (contentLength > buffer.remaining()) {
					//content长度大于buffer内剩余数据,说明包没有完全接收.回退到mark(),并返回
					//等待下次追加
					buffer.reset();
					byte[] rest = new byte[buffer.remaining()];
					buffer.get(rest);
					buffer.free();
					context.setBuffer(IoBuffer.allocate(80,false).setAutoExpand(true));
					context.appendBytes(rest);
					return;
				}
				if (contentLength <= buffer.remaining()) {
					//content长度小于等于剩余数据,说明此pack已经完全接受.可以读取字符串了.
					//但是这里limit的位置不一定指向字符串的结尾.所以要手动指定下
					int oldLimit = buffer.limit();
					buffer.limit(buffer.position()+contentLength);
					content=buffer.getString(charset.newDecoder());
					out.write("flag:"+flag+",content: "+content);
					
					//回退原来的limit
					buffer.limit(oldLimit);
					//进入下一次while循环
				}
			//如果数据读取完成后,剩余数据不满一个包头,将结束循环
			}//while结束
			/*如果有剩余数据,并且不满一个包头
			 * 取出buffer内剩余的数据,并追加到新context buffer
			 */
			if (buffer.hasRemaining() && buffer.remaining() <5) {
				byte[] rest = new byte[buffer.remaining()];
				buffer.get(rest);
				buffer.free();
				context.setBuffer(IoBuffer.allocate(80,false).setAutoExpand(true));
				context.appendBytes(rest);
				return;
			}else if(!buffer.hasRemaining()){
				//如果正好读完,重新创建buffer
				buffer.free();
				context.setBuffer(IoBuffer.allocate(80,false).setAutoExpand(true));
				return;
			}
		}
	}

	@Override
	public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
		
	}

	@Override
	public void dispose(IoSession session) throws Exception {
		Context context = (Context) session.getAttribute(CONTEXT);
		if (context != null) {
			session.removeAttribute(CONTEXT);
		}
	}
	
	/**
	 * IoSession 对象,是用来描述,通信会话的,这个session是可以添加属性对象的.
	 * 上边的AttributeKey就是这个属性中的键的值.
	 */
	public Context getContext(IoSession session) {
		Context context = (Context) session.getAttribute(CONTEXT);
		//如果session的这个属性中,没有值,就设定一个新值
		if (context == null) {
			System.out.println("there is no context");
			context = new Context();
			session.setAttribute(CONTEXT, context);
		}
		return context;
	}
	
	private class Context{
		private final CharsetDecoder decoder;
		private IoBuffer buffer;
		private Context() {
			decoder = charset.newDecoder();
			buffer = IoBuffer.allocate(80).setAutoExpand(true);
		}
		public IoBuffer getBuffer() {
			return buffer;
		}
		public void setBuffer(IoBuffer buffer) {
			this.buffer = buffer;
		}
		@SuppressWarnings("unused")
		public CharsetDecoder getDecoder() {
			return decoder;
		}
		public void append(IoBuffer buffer) {
			this.buffer.put(buffer);
		}
		public void appendBytes(byte[] rest) {
			this.buffer.put(rest);
		}
		@SuppressWarnings("unused")
		public void reset(){
			decoder.reset();
		}
	}
}
